/**
 * Copyright (C), 2017-2018, XXX有限公司
 * FileName: AsmWriter
 * Author:   zengjian
 * Date:     2018/8/22 19:15
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package parser.asm;

import org.junit.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
import java.util.Arrays;

import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;

/**
 * 〈〉<br>
 * 〈一句话描述〉
 *
 * @author zengjian
 * @create 2018/8/22 19:15
 */
public class AsmWriter {

    @Test
    public void testWriter() throws Exception {
        ClassWriter classWriter = new ClassWriter(0);
        // 确定类header信息
        classWriter.visit(Opcodes.V1_7, ACC_PUBLIC, "BBB", null, "java/lang/Object", /*new String[]{"parser/asm/AAA", "parser/asm/CCC"}*/null);
        // 定义类的属性
        classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null).visitEnd();
        classWriter.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null).visitEnd();
        classWriter.visitField(ACC_PUBLIC, "name", "Ljava/lang/String;", null, "1").visitEnd();
        // 定义类的方法
//        classWriter.visitMethod(
//                Opcodes.ACC_PUBLIC,
//                "getName", "()Ljava/lang/String", "null", null)
//                .visitEnd();

        classWriter.visitEnd();
        byte[] data = classWriter.toByteArray();
        File file = new File("E://BBB.class");
        FileOutputStream fout = new FileOutputStream(file);
        fout.write(data);
        fout.close();
        ClassReader classReader = new ClassReader(new FileInputStream("E://BBB.class"));
        System.out.println(classReader.toString());
        System.out.println(classReader.getClassName());
        System.out.println(Arrays.toString(classReader.getInterfaces()));
        Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.name");
        Object proxy = clazz.newInstance();
        Method m = clazz.getMethod("xx");
        m.setAccessible(true);
        m.invoke(this);
    }

    @Test
    public void createOneClassInstance() throws Exception {
        ClassWriter writer = new ClassWriter(COMPUTE_MAXS);
        writer.visit(Opcodes.V1_8, ACC_PUBLIC, "One", null, "java/lang/Object", null);
        MethodVisitor constructor = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        constructor.visitCode();
        constructor.visitVarInsn(Opcodes.ALOAD, 0);
        constructor.visitMethodInsn(
                Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
        constructor.visitInsn(Opcodes.RETURN);
        constructor.visitMaxs(1, 1);
        constructor.visitEnd();
        MethodVisitor methodVisitor = writer.visitMethod(ACC_PUBLIC, "getName", "()V", null, null);
        methodVisitor.visitCode();
        methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                "java/lang/System",
                "out",
                "Ljava/io/PrintStream;");
        methodVisitor.visitLdcInsn("Hello world!");
        methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream",
                "println",
                "(Ljava/lang/String;)V");
        methodVisitor.visitInsn(Opcodes.RETURN);
        methodVisitor.visitMaxs(1, 0);
        methodVisitor.visitEnd();
        writer.visitEnd();
        byte[] bytes = writer.toByteArray();
        Loader loader = new Loader(bytes);
        Class oneClazz = loader.findClass("One");
        Object one = oneClazz.newInstance();
        Method m = oneClazz.getDeclaredMethod("getName", null);
        m.setAccessible(true);
        m.invoke(one, null);
        System.out.println(oneClazz);
        System.out.println(one);
        System.out.println(m);
    }

    class Loader extends ClassLoader {

        private byte[] bytes;

        public Loader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {


            return super.loadClass(name);
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            if (name.contains("One")) {
                return this.defineClass("One", bytes, 0, bytes.length);
            }
            return super.findClass(name);
        }
    }
}